package com.ewivt.excel.utils;

import com.ewivt.excel.model.Distance;
import com.ewivt.excel.model.Location;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;

@Slf4j
public class SingleRouteAlgorithm {
    //初始温度
    private static double INIT_T = 120;

    //最低温度
    private static double LOWEST_T = 0.001;

    //迭代次数
    private static int ITERATION = 1000;

    //当连续多次都不接受新的状态，开始改变温度
    private static int MIN_NOT_CHANGE_LIMIT = 150;

    public List<Location> getSingleRoute(List<Location> locationList, List<Distance> distanceList){
        List<Location> result = new ArrayList<>();
        int[] initDistanceArray = getInitDistanceArray((locationList.size()));
        double[][] p2pDistanceArray = getP2pDistanceArray(locationList,distanceList);
        double initDistance = calculateInitDistance(p2pDistanceArray,initDistanceArray);
        double current_temp = INIT_T;
        Random random = new Random();

        while (current_temp>LOWEST_T){
            int count_m =0;
            int count_inter = 0;
            while (count_m<MIN_NOT_CHANGE_LIMIT&&count_inter<ITERATION){
                int i =0;
                int j = 0;
                int[] newInitDistanceArray =initDistanceArray.clone(); //getInitDistanceArray((locationList.size()));
                while (i==j){
                    i = 1+ random.nextInt(locationList.size()-1);
                    j = 1+ random.nextInt(locationList.size()-1);
                }
                //两两交换
                int iDis = newInitDistanceArray[i];
                int jDis = newInitDistanceArray[j];
                newInitDistanceArray[i] = jDis;
                newInitDistanceArray[j] = iDis;
                double newInitDistance = calculateInitDistance(p2pDistanceArray,newInitDistanceArray);
                double distanceDifference = newInitDistance - initDistance;
                float randomFloat = random.nextFloat();
                double exp_d = Math.exp(-distanceDifference/current_temp);
                if (distanceDifference<0){
                    initDistanceArray = newInitDistanceArray;
                    initDistance = newInitDistance;
                }
                else if(exp_d>randomFloat){
                    initDistanceArray = newInitDistanceArray;
                    initDistance = newInitDistance;
                }else {
                    count_m+=1;
                }
                count_inter+=1;
            }
            current_temp=0.99*current_temp;
        }
        double minDistance = initDistance;
        log.info("最短距离："+minDistance);
        int[] minDistanceArray = initDistanceArray;
        Object[] locationArray = locationList.toArray();
        for (int index = 0 ;index< locationArray.length;index++){
            result.add((Location)locationArray[minDistanceArray[index]]);
        }
        return result;
    }

    /**
     * 获取LocationList中每个点之间的距离数组
     * */
    private double[][] getP2pDistanceArray(List<Location> locationList, List<Distance> distanceList){
        double[][] distanceMap = new double[locationList.size()][locationList.size()];
        int mainIndex = 0;
        for (Location startLocation: locationList){
            double[] subDistance = new double[locationList.size()];
            int subIndex = 0;
            for (Location endLocation: locationList){
                subDistance[subIndex] = getInstance(startLocation.getSeq(),endLocation.getSeq(),distanceList);
                subIndex+=1;
            }
            distanceMap[mainIndex] = subDistance;
            mainIndex+=1;
        }
        return distanceMap;
    }

    /**
     * 计算总的距离
     * */
    private double calculateInitDistance(double[][] p2pDistanceArray,int[] initDistanceArray){
        double distance = 0;
        for (int index =0;index<initDistanceArray.length-1;index++){
            distance+=p2pDistanceArray[initDistanceArray[index]][initDistanceArray[index+1]];
        }
        return distance;
    }

    /**
     * 通过起点编码和终点编码获取两点之间的距离
     * */
    private double getInstance(String startCode,String endCode,List<Distance> distanceList){
        Optional<Distance> distanceOptional = distanceList.stream().filter(o->o.getStartCode().equals(startCode)&&o.getEndCode().equals(endCode)).findFirst();
        if (distanceOptional.isPresent()){
            return distanceOptional.get().getDriveDistance();
        }
        return 0;
    }

    /**
     * 获取初始化数组
     * */
    private int[] getInitDistanceArray(int size){
        int[] initDistance = new int[size];
        for (int index=0;index<size;index++){
            initDistance[index] = index;
        }
        return initDistance;
    }
}
